package config

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

func findFieldWithLowerName(rv reflect.Value, name string) reflect.Value {
	lowerName := strings.ToLower(name)
	for i := 0; i < rv.Type().NumField(); i++ {
		if strings.ToLower(rv.Type().Field(i).Name) == lowerName {
			return rv.Field(i)
		}
	}
	return reflect.Value{}
}

func findFieldInBody(body reflect.Value, desc *ConfigDesc, path string) (reflect.Value, error) {
	if body.Kind() == reflect.Invalid {
		return reflect.Value{}, nil
	}

	rv := body
	for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
		rv = rv.Elem()
	}

	ss := strings.SplitN(path, ".", 2)
	fieldName := ss[0]
	if strings.HasSuffix(ss[0], "]") {
		ssMap := strings.SplitN(ss[0], "[", 2)
		if len(ssMap) != 2 {
			return reflect.Value{}, fmt.Errorf("config: invalid path %s", path)
		}
		fieldName = ssMap[0]
		index := ssMap[1][:len(ssMap[1])-1]
		child, exist := desc.ChildrenMap[strings.ToLower(fieldName)]
		if !exist {
			return reflect.Value{}, nil
		}
		rvMap := findFieldWithLowerName(rv, fieldName)
		if !rvMap.IsValid() {
			rvMap.Set(reflect.MakeMapWithSize(rvMap.Type(), 4))
		}
		sub, exist := child[index]
		if !exist {
			return reflect.Value{}, nil
		}
		return findFieldInBody(rvMap.MapIndex(reflect.ValueOf(index)), sub, ss[1])
	} else if len(ss) > 1 {
		child, exist := desc.Children[strings.ToLower(fieldName)]
		if !exist {
			return reflect.Value{}, nil
		}
		return findFieldInBody(findFieldWithLowerName(rv, fieldName), child, ss[1])
	}

	rvf := findFieldWithLowerName(rv, fieldName)
	if rvf.Kind() == reflect.Invalid {
		return reflect.Value{}, nil
	}

	return rvf, nil
}

func setValueFromString(val reflect.Value, valStr string) error {
	switch val.Kind() {
	case reflect.String:
		val.SetString(valStr)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		v, err := strconv.ParseInt(valStr, 10, 64)
		if err != nil {
			return fmt.Errorf("config string [%s] is not int", valStr)
		}
		val.SetInt(v)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		v, err := strconv.ParseUint(valStr, 10, 64)
		if err != nil {
			return fmt.Errorf("config string [%s] is not uint", valStr)
		}
		val.SetUint(v)
	case reflect.Float32, reflect.Float64:
		v, err := strconv.ParseFloat(valStr, 64)
		if err != nil {
			return fmt.Errorf("config string [%s] is not float", valStr)
		}
		val.SetFloat(v)
	case reflect.Bool:
		v, err := strconv.ParseBool(valStr)
		if err != nil {
			return fmt.Errorf("config string [%s] is not bool", valStr)
		}
		val.SetBool(v)
	default:
		return fmt.Errorf("config string [%s] to un-support field type [%v]", valStr, val.Type())
	}

	return nil
}
